"""
Outputs web.py docs as html
version 2.0: documents all code, and indents nicely.
By Colin Rothwell (TheBoff)
"""

import inspect
import sys

import markdown

from web.net import websafe

sys.path.insert(0, "..")


ALL_MODULES = [
    "web.application",
    "web.contrib.template",
    "web.db",
    "web.debugerror",
    "web.form",
    "web.http",
    "web.httpserver",
    "web.net",
    "web.session",
    "web.template",
    "web.utils",
    "web.webapi",
    "web.wsgi",
]

item_start = '<code class="%s">'
item_end = "</code>"

indent_amount = 30

doc_these = (  # These are the types of object that should be docced
    "module",
    "classobj",
    "instancemethod",
    "function",
    "type",
    "property",
)

not_these_names = (  # Any particular object names that shouldn't be doced
    "fget",
    "fset",
    "fdel",
    "storage",  # These stop the lower case versions getting docced
    "memoize",
    "iterbetter",
    "capturesstdout",
    "profile",
    "threadeddict",
    "d",  # Don't know what this is, but only only conclude it shouldn't be doc'd
)

css = """
<style type="text/css">
.module {
    font-size: 130%;
    font-weight: bold;
}

.function, .class, .type {
    font-size: 120%;
    font-weight: bold;
}

.method, .property {
    font-size: 115%;
    font-weight: bold;
}

.ts {
    font-size: small;
    font-weight: lighter;
    color: grey;
}

#contents_link {
    position: fixed;
    top: 0;
    right: 0;
    padding: 5px;
    background: rgba(255, 255, 255, 0.5);
}

#contents_link a:hover {
    font-weight: bold;
}
</style>
"""


indent_start = '<div style="margin-left:%dpx">'
indent_end = "</div>"

header = """
<div id="contents_link">
<a href="#top">Back to contents</a>
</div>
"""


def type_string(ob):
    return str(type(ob)).split("'")[1]


def ts_css(text):
    """applies nice css to the type string"""
    return '<span class="ts">%s</span>' % text


def arg_string(func):
    """Returns a nice argstring for a function or method"""
    return inspect.formatargspec(*inspect.getargspec(func))


def recurse_over(ob, name, indent_level=0):
    ts = type_string(ob)
    if ts not in doc_these:
        return  # stos what shouldn't be docced getting docced
    if indent_level > 0 and ts == "module":
        return  # Stops it getting into the stdlib
    if name in not_these_names:
        return  # Stops things we don't want getting docced

    indent = indent_level * indent_amount  # Indents nicely
    ds_indent = indent + (indent_amount / 2)
    if indent_level > 0:
        print(indent_start % indent)

    argstr = ""
    if ts.endswith(("function", "method")):
        argstr = arg_string(ob)
    elif ts in {"classobj", "type"}:
        if ts == "classobj":
            ts = "class"
        if hasattr(ob, "__init__"):
            if type_string(ob.__init__) == "instancemethod":
                argstr = arg_string(ob.__init__)
        else:
            argstr = "(self)"
    if ts == "instancemethod":
        ts = "method"  # looks much nicer

    ds = inspect.getdoc(ob)
    if ds is None:
        ds = ""
    ds = markdown.Markdown(ds)

    mlink = '<a name="%s">' % name if ts == "module" else ""
    mend = "</a>" if ts == "module" else ""
    print(
        "".join(
            (
                "<p>",
                ts_css(ts),
                item_start % ts,
                " ",
                mlink,
                name,
                websafe(argstr),
                mend,
                item_end,
                "<br />",
            )
        )
    )
    print("".join((indent_start % ds_indent, ds, indent_end, "</p>")))
    # Although ''.join looks weird, it's a lot faster is string addition
    members = ""

    if hasattr(ob, "__all__"):
        members = ob.__all__
    else:
        members = [item for item in dir(ob) if not item.startswith("_")]

    if "im_class" not in members:
        for member_name in members:
            recurse_over(getattr(ob, member_name), member_name, indent_level + 1)
    if indent_level > 0:
        print(indent_end)


def main(modules=None):
    modules = modules or ALL_MODULES
    print("<div>")  # Stops markdown vandalising my html.
    print(css)
    print(header)
    print("<ul>")
    for name in modules:
        print('<li><a href="#{name}">{name}</a></li>'.format(**dict(name=name)))
    print("</ul>")
    for name in modules:
        try:
            mod = __import__(name, {}, {}, "x")
            recurse_over(mod, name)
        except ImportError as e:
            print(f"Unable to import module {name} (Error: {e})", file=sys.stderr)
            pass
    print("</div>")


if __name__ == "__main__":
    main(sys.argv[1:])
